(function(g,webglUtils){
	if(g){
		g.webglUtils=webglUtils
	}else{
		this.webglUtils=webglUtils
	}
})(this,
class webglUtils {
	constructor(){

	}
	/**
	 * 
	 * @param {id and class or tag} selector 
	 * @param {name:string,min:number,max:number,precision:number} options 
	 */
	static setupSlider(selector,options={
		name,min:0,max:100,precision:0
	}){
		const dom=document.querySelector(selector)
		if(!dom||!options)return 
		if (!options.name) {
			options.name = selector.substring(1);
		}
		const name=options.name
		let min=options.min||0,
		max=options.max||100,
		step=options.step||1,
		value=isNaN(options.value)?options.min||0:options.value
		const precision = options.precision || 0;
		const uiPrecision = options.uiPrecision === undefined ? precision : options.uiPrecision;
		min /= step;
		max /= step;
		value /= step;
		dom.innerHTML=`
		<div class="ui">
			<div class="ui-label">${name}</div>
			<input class="ui-slider" type="range" min="${min}" max="${max}" value="${value}" />
			<div class="ui-value"></div>
		</div>
		`
		const slider=dom.querySelector(".ui-slider")
		const valueEl=dom.querySelector(".ui-value")
		
		const fun=options.handle
		slider.addEventListener('input', handleChange);
    	slider.addEventListener('change', handleChange);
		function updateValue(value) {
			valueEl.textContent = (value * step).toFixed(uiPrecision);
		}
		
		updateValue(value)
		function handleChange(e){
			const value=parseInt(e.target.value)
			updateValue(value)
			fun(e,{
				value:value*step,
			})
		}
		return {
			updateValue(v){
				updateValue(value)
				v /= step;
				slider.value = v;
				updateValue(v);

			}
		}
	}
	/**
	 * 
	 * 1弧度=π/180
	 * 1度=180/π
	 * 一弧度等于π除180
	 * 一度等于180/π
	 * @param {弧度} radians 
	 */
	static radiansToDegrees(radians) {//弧度到度数
		return 180/Math.PI*radians
	}
	/**
	 * 
	 * @param {度数} degrees 
	 */
	static degreesToRadians(degrees) {//度数到弧度
		return Math.PI/180*degrees
	}

	static Mat3=class Mat3{
		constructor(...args) {
			return args
		}
		static init() {
		  return webglUtils.Mat3.identity()
		}
		static create() {
		  return webglUtils.Mat3.identity()
		}
		static projection(width, height) {
			/*
			1. 原矩阵
			[
				1/width, 0, 0,
				0, 1/height, 0,
				0, 0, 1,
			]

			2. 矩阵缩放1/width和1/height
			[
				1/width, 0, 0,
				0, 1/height, 0,
				0, 0, 1,
			]
			3. 再缩放2，2
			[
				2/width, 0, 0,
				0, 2/height, 0,
				0, 0, 1,
			]

			在平移-1，-1
			[
				2/width, 0, 0,
				0, 2/height, 0,
				-1, -1, 1,
			]

			再缩放1,-1
			[
				2/width, 0, 0,
				0, -2/height, 0,
				-1, 1, 1,
			]
			
			需要注意的是矩阵缩放负数y轴对应的列全部变号了
			也就是原来平移-1变成了1原来缩放2/height变成了-2/height
			分数乘数分子乘于数就好了
			
			*/
			return [
			  2 / width, 0, 0,
			  0, -2 / height, 0,
			  -1, 1, 1
			];
		}
		static identity() {
		  return [
			1, 0, 0,
			0, 1, 0,
			0, 0, 1,
		  ];
		}
		static translation(x,y){
			return new Mat3(
				1,0,0,
				0,1,0,
				x,y,1)
		}
		static rotation(angleInRadians){
			var c = Math.cos(angleInRadians);
			var s = Math.sin(angleInRadians);
			return new Mat3(
				c,-s,0,
				s,c,0,
				0,0,1)
		}
		static scaling(sx,sy){
			return new Mat3(
				sx,0,0,
				0,sy,0,
				0,0,1)
		}
		static translate(m, tx, ty) {
			return webglUtils.Mat3.multiply(m, webglUtils.Mat3.translation(tx, ty));
		}
		 
		static rotate(m, angleInRadians) {
			return webglUtils.Mat3.multiply(m, webglUtils.Mat3.rotation(angleInRadians));
		}
		 
		static scale(m, sx, sy) {
			return webglUtils.Mat3.multiply(m, webglUtils.Mat3.scaling(sx, sy));
		}
		static multiply(a,b){//第一个矩阵的列乘第二个矩阵的行
			let a1=a[0]
			let a2=a[1]
			let a3=a[2]
			let a4=a[3]
			let a5=a[4]
			let a6=a[5]
			let a7=a[6]
			let a8=a[7]
			let a9=a[8]

			let b1=b[0]
			let b2=b[1]
			let b3=b[2]
			let b4=b[3]
			let b5=b[4]
			let b6=b[5]
			let b7=b[6]
			let b8=b[7]
			let b9=b[8]
			/*
			 a=[
				a1,a2,a3,
				a4,a5,a6,
                a7,a8,a9
			 ]
			b=[
                b1,b2,b3,
                b4,b5,b6,
                b7,b8,b9,
			]

			 */
			// return new Mat3(
            //     a1*b1+a2*b4+a3*b7 , a1*b2+a2*b5+a3*b8 , a1*b3+a2*b6+a3*b9,
            //     a4*b1+a5*b4+a6*b7 , a4*b2+a5*b5+a6*b8 , a4*b3+a5*b6+a6*b9,
            //     a7*b1+a8*b4+a9*b7 , a7*b2+a8*b5+a9*b8 , a7*b3+a8*b6+a9*b9
			// )

			return new Mat3(//麻烦..
                a1*b1 + a4*b2 + a7*b3,a2*b1 + a5*b2 + a8*b3,a3*b1 + a6*b2 + a9*b3,
                a1*b4 + a4*b5 + a7*b6,a2*b4 + a5*b5 + a8*b6,a3*b4 + a6*b5 + a9*b6,
                a1*b7 + a4*b8 + a7*b9,a2*b7 + a5*b8 + a8*b9,a3*b7 + a6*b8 + a9*b9,
			)
			//
		}
	}
	static Mat4=class Mat4{
		constructor(...args) {
			return args
		}
		static init() {
		  return webglUtils.Mat4.identity()
		}
		static projection(width, height,depth) {
			return [
				2 / width, 0, 0, 0,
				0, -2 / height, 0, 0,
				0, 0, 2 / depth, 0,
				-1, 1, 0,1
			];
		}
		static create() {
		  return webglUtils.Mat4.identity()
		}
		static identity() {
		  return [
			1, 0, 0,0,
			0, 1, 0,0,
			0, 0, 1,0,
			0, 0, 0,1
		  ];
		}
		static transformPoint(m, v, dst) {
			dst = dst || new Float32Array(3);
			var v0 = v[0];
			var v1 = v[1];
			var v2 = v[2];
			var d = v0 * m[0 * 4 + 3] + v1 * m[1 * 4 + 3] + v2 * m[2 * 4 + 3] + m[3 * 4 + 3];
		
			dst[0] = (v0 * m[0 * 4 + 0] + v1 * m[1 * 4 + 0] + v2 * m[2 * 4 + 0] + m[3 * 4 + 0]) / d;
			dst[1] = (v0 * m[0 * 4 + 1] + v1 * m[1 * 4 + 1] + v2 * m[2 * 4 + 1] + m[3 * 4 + 1]) / d;
			dst[2] = (v0 * m[0 * 4 + 2] + v1 * m[1 * 4 + 2] + v2 * m[2 * 4 + 2] + m[3 * 4 + 2]) / d;
			return dst;
		}
		static translation(tx,ty,tz){
			return new Mat4(
				1,0,0,0,
				0,1,0,0,
				0,0,1,0,
				tx,ty,tz,1,
			)
		}
		static xRotation(angleInRadians){
			var c = Math.cos(angleInRadians);
			var s = Math.sin(angleInRadians);
			return new Mat4(
				1, 0, 0, 0,
				0, c, s, 0,
				0, -s, c, 0,
				0, 0, 0, 1
			)
		}
		static yRotation(angleInRadians){
			var c = Math.cos(angleInRadians);
			var s = Math.sin(angleInRadians);
			return new Mat4(
				c, 0, -s, 0,
				0, 1, 0, 0,
				s, 0, c, 0,
				0, 0, 0, 1
			)
		}
		static zRotation(angleInRadians){
			var c = Math.cos(angleInRadians);
			var s = Math.sin(angleInRadians);
			return new Mat4(
				c,-s,0,0,
				s,c,0,0,
				0,0,1,0,
				0,0,0,1
			)
		}
		static scaling(sx,sy,sz){
			return new Mat4(
				sx, 0,  0,  0,
				0, sy,  0,  0,
				0,  0, sz,  0,
				0,  0,  0,  1
			)
		}
		static perspective(fieldOfViewInRadians, aspect, near, far) {
			var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians);
			var rangeInv = 1.0 / (near - far);
		
			return [
			  f / aspect, 0, 0, 0,
			  0, f, 0, 0,
			  0, 0, (near + far) * rangeInv, -1,
			  0, 0, near * far * rangeInv * 2, 0
			];
		}
		static translate(m, tx,ty,tz) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.translation(tx,ty,tz));
		}
		static scale(m, sx,sy,sz) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.scaling(sx,sy,sz));
		}
		static rotate(m, rx,ry,rz) {
			return webglUtils.Mat4.rotateZ(webglUtils.Mat4.rotateY(webglUtils.Mat4.rotateX(m,rx),ry),rz)
		}
		static translateX(m, tx) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.translation(tx, 0, 0));
		}
		static translateY(m, ty) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.translation(0, ty, 0));
		}
		static translateZ(m, tz) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.translation(0, 0, tz));
		}
		static rotateX(m, angleInRadians) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.xRotation(angleInRadians));
		}
		static rotateY(m, angleInRadians) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.yRotation(angleInRadians));
		}
		static rotateZ(m, angleInRadians) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.zRotation(angleInRadians));
		}
		static xRotate(m, angleInRadians) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.xRotation(angleInRadians));
		}	
		static yRotate(m, angleInRadians) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.yRotation(angleInRadians));
		}		
		static zRotate(m, angleInRadians) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.zRotation(angleInRadians));
		}
		static scaleX(m, sx) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.scaling(sx, 1,1));
		}
		static scaleY(m, sy) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.scaling(1, sy,1));
		}
		static scaleZ(m, sz) {
			return webglUtils.Mat4.multiply(m, webglUtils.Mat4.scaling(1, 1,sz));
		}
		static multiply(a,b){//第一个矩阵的列乘第二个矩阵的行
			const mat4A={

			}
			const mat4B={

			}
			for (let row = 0; row < 4; row++) {
				for (let column = 0; column < 4; column++) {
					mat4A[`${row}${column}`]=a[row*4+column]
					mat4B[`${row}${column}`]=b[row*4+column]
				}
			}
			/**
			[行 列]
			00 01 02 03
			10 11 12 13
			20 21 22 23
			30 31 32 33
			 */
			return new Mat4(
				mat4A["00"]*mat4B["00"]+mat4A["10"]*mat4B["01"]+mat4A["20"]*mat4B["02"]+mat4A["30"]*mat4B["03"],
				mat4A["01"]*mat4B["00"]+mat4A["11"]*mat4B["01"]+mat4A["21"]*mat4B["02"]+mat4A["31"]*mat4B["03"],
				mat4A["02"]*mat4B["00"]+mat4A["12"]*mat4B["01"]+mat4A["22"]*mat4B["02"]+mat4A["32"]*mat4B["03"],
				mat4A["03"]*mat4B["00"]+mat4A["13"]*mat4B["01"]+mat4A["23"]*mat4B["02"]+mat4A["33"]*mat4B["03"],
				
				mat4A["00"]*mat4B["10"]+mat4A["10"]*mat4B["11"]+mat4A["20"]*mat4B["12"]+mat4A["30"]*mat4B["13"],
				mat4A["01"]*mat4B["10"]+mat4A["11"]*mat4B["11"]+mat4A["21"]*mat4B["12"]+mat4A["31"]*mat4B["13"],
				mat4A["02"]*mat4B["10"]+mat4A["12"]*mat4B["11"]+mat4A["22"]*mat4B["12"]+mat4A["32"]*mat4B["13"],
				mat4A["03"]*mat4B["10"]+mat4A["13"]*mat4B["11"]+mat4A["23"]*mat4B["12"]+mat4A["33"]*mat4B["13"],
				
				mat4A["00"]*mat4B["20"]+mat4A["10"]*mat4B["21"]+mat4A["20"]*mat4B["22"]+mat4A["30"]*mat4B["23"],
				mat4A["01"]*mat4B["20"]+mat4A["11"]*mat4B["21"]+mat4A["21"]*mat4B["22"]+mat4A["31"]*mat4B["23"],
				mat4A["02"]*mat4B["20"]+mat4A["12"]*mat4B["21"]+mat4A["22"]*mat4B["22"]+mat4A["32"]*mat4B["23"],
				mat4A["03"]*mat4B["20"]+mat4A["13"]*mat4B["21"]+mat4A["23"]*mat4B["22"]+mat4A["33"]*mat4B["23"],

				mat4A["00"]*mat4B["30"]+mat4A["10"]*mat4B["31"]+mat4A["20"]*mat4B["32"]+mat4A["30"]*mat4B["33"],
				mat4A["01"]*mat4B["30"]+mat4A["11"]*mat4B["31"]+mat4A["21"]*mat4B["32"]+mat4A["31"]*mat4B["33"],
				mat4A["02"]*mat4B["30"]+mat4A["12"]*mat4B["31"]+mat4A["22"]*mat4B["32"]+mat4A["32"]*mat4B["33"],
				mat4A["03"]*mat4B["30"]+mat4A["13"]*mat4B["31"]+mat4A["23"]*mat4B["32"]+mat4A["33"]*mat4B["33"]
			)
		}
		static inverse(m) {
			var m00 = m[0 * 4 + 0];
			var m01 = m[0 * 4 + 1];
			var m02 = m[0 * 4 + 2];
			var m03 = m[0 * 4 + 3];
			var m10 = m[1 * 4 + 0];
			var m11 = m[1 * 4 + 1];
			var m12 = m[1 * 4 + 2];
			var m13 = m[1 * 4 + 3];
			var m20 = m[2 * 4 + 0];
			var m21 = m[2 * 4 + 1];
			var m22 = m[2 * 4 + 2];
			var m23 = m[2 * 4 + 3];
			var m30 = m[3 * 4 + 0];
			var m31 = m[3 * 4 + 1];
			var m32 = m[3 * 4 + 2];
			var m33 = m[3 * 4 + 3];
			var tmp_0  = m22 * m33;
			var tmp_1  = m32 * m23;
			var tmp_2  = m12 * m33;
			var tmp_3  = m32 * m13;
			var tmp_4  = m12 * m23;
			var tmp_5  = m22 * m13;
			var tmp_6  = m02 * m33;
			var tmp_7  = m32 * m03;
			var tmp_8  = m02 * m23;
			var tmp_9  = m22 * m03;
			var tmp_10 = m02 * m13;
			var tmp_11 = m12 * m03;
			var tmp_12 = m20 * m31;
			var tmp_13 = m30 * m21;
			var tmp_14 = m10 * m31;
			var tmp_15 = m30 * m11;
			var tmp_16 = m10 * m21;
			var tmp_17 = m20 * m11;
			var tmp_18 = m00 * m31;
			var tmp_19 = m30 * m01;
			var tmp_20 = m00 * m21;
			var tmp_21 = m20 * m01;
			var tmp_22 = m00 * m11;
			var tmp_23 = m10 * m01;
		
			var t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) -
				(tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
			var t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) -
				(tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
			var t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) -
				(tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
			var t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) -
				(tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);
		
			var d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
		
			return [
			  d * t0,
			  d * t1,
			  d * t2,
			  d * t3,
			  d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) -
					(tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)),
			  d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) -
					(tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)),
			  d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) -
					(tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)),
			  d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) -
					(tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)),
			  d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) -
					(tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)),
			  d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) -
					(tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)),
			  d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) -
					(tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)),
			  d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) -
					(tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)),
			  d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) -
					(tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)),
			  d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) -
					(tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)),
			  d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) -
					(tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)),
			  d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) -
					(tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02))
			];
		}
		static lookAt(cameraPosition, target, up) {
			var zAxis = webglUtils.normalize(
				webglUtils.subtractVectors(cameraPosition, target));
			var xAxis = webglUtils.normalize(webglUtils.cross(up, zAxis));
			var yAxis = webglUtils.normalize(webglUtils.cross(zAxis, xAxis));
			return [
			   xAxis[0], xAxis[1], xAxis[2], 0,
			   yAxis[0], yAxis[1], yAxis[2], 0,
			   zAxis[0], zAxis[1], zAxis[2], 0,
			   cameraPosition[0],
			   cameraPosition[1],
			   cameraPosition[2],
			   1,
			];
		}
		static transpose(m) {
			return [
				m[0], m[4], m[8], m[12],
				m[1], m[5], m[9], m[13],
				m[2], m[6], m[10], m[14],
				m[3], m[7], m[11], m[15],
			];
		}
	}
	/**
	 * 点乘
	 * @param {Array} a [x,y,z]
	 * @param {Array} b [x,y,z]
	 * @return [x,y,z]
	 */
	static cross(a, b) {
		/**
		 [0,1,2] 
		 [0,1,2] 
		 
		 x y*z-z*y
		 y z*x-x*z
		 z x*y-y*x
		 */
		return [a[1] * b[2] - a[2] * b[1],
				a[2] * b[0] - a[0] * b[2],
				a[0] * b[1] - a[1] * b[0]];
	}
	/**
	 * 归一化
	 * @param {Array} vec3 [x,y,z]
	 * @return [x,y,z]
	 */
	static normalize(vec3) {
		//[0,1,2]
		/*
			|\
		   x| \y
			|  \
			——————
			  z
		x*x+y*y+z*z
		根号 x平方y平方z平方
		*/
		vec3[2]=vec3[2]||0
		const length = webglUtils.lenV(vec3);
		// const length = Math.sqrt(vec3[0] * vec3[0] + vec3[1] * vec3[1] + (vec3[2]||0)* (vec3[2]||0));
		// make sure we don't divec3ide by 0.
		if (length > 0.00001) {
		  return [vec3[0] / length, vec3[1] / length, vec3[2] / length];
		} else {
		  return [0, 0, 0];
		}
	}
	/**
	 * 向量相减
	 * @param {*} a [x,y,z]
	 * @param {*} b [x,y,z]
	 * @return [x,y,z]
	 */
	static subtractVectors(a, b) {

		return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
	}
	/**
	 * 坐标计算长度
	 * @param {*} x 
	 * @param {*} y 
	 * @param {*} z 
	 * @return 斜边长
	 */
	static len(x,y,z=0){
		/*
		let 对边=1
		let 邻边=1
		let 斜边=Math.sqrt(对边*对边+邻边*邻边)
		let rad=Math.asin(对边/斜边)
		*/
		return Math.sqrt(x*x+y*y+z*z)
	}
	static lenV(vec3){
		/*
		let 对边=1
		let 邻边=1
		let 斜边=Math.sqrt(对边*对边+邻边*邻边)
		let rad=Math.asin(对边/斜边)
		*/
		return Math.sqrt(vec3[0] * vec3[0] + vec3[1] * vec3[1] + (vec3[2]||0)* (vec3[2]||0));
	}
	/**
	 * 点乘
	 * @param {*} x 
	 * @param {*} y 
	 * @return 
	 */
	static dot(vec_1,vec_2){
		/*
		先相乘再相加
		*/
		return vec_1[0]*vec_2[0]+(vec_1[1]||0)*(vec_2[1]||0)+(vec_1[2]||0)*(vec_2[2]||0)
		// let min,max
		// if(vec1.length>vec2.length){
		// 	min=vec_2
		// 	max=vec_1
		// }else{
		// 	min=vec_1
		// 	max=vec_2
		// }
		// return min.reduce((sum,i,index)=>{
		// 	return sum+i*(max[index]||0)
		// },0)
		// return vec_1.reduce((sum,i,index)=>{
		// 	return sum+i*(vec_2[index]||0)
		// },0)

	}
	static createCube(width){
		/*
				h1		h4
			q1	 q4
				h2		h3
			q2	 q3
		*/
		const q1=[width/-2,width/2,width/2],q2=[width/-2,width/-2,width/2],q3=[width/2,width/-2,width/2],q4=[width/2,width/2,width/2],h1=[-width/2,width/2,width/-2],h2=[width/-2,width/-2,width/-2],h3=[width/2,width/-2,width/-2],h4=[width/2,width/2,width/-2]
		const pos=[
			...q1,...q2,...q3,...q3,...q4,...q1,//前
			...h3,...h2,...h1,...h1,...h4,...h3,//后

			...h2,...q2,...q1,...h2,...q1,...h1,//左
			...q4,...q3,...h3,...h3,...h4,...q4,//右

			...q1,...q4,...h4,...h4,...h1,...q1,//上
			...h3,...q3,...q2,...q2,...h2,...h3,//下
		]
		const U=[0,1,0],D=[0,-1,0],L=[-1,0,0],R=[1,0,0],B=[0,0,1],A=[0,0,-1]
		const normal=[
			...B,...B,...B,...B,...B,...B,
			...A,...A,...A,...A,...A,...A,
			...L,...L,...L,...L,...L,...L,
			...R,...R,...R,...R,...R,...R,
			...U,...U,...U,...U,...U,...U,
			...D,...D,...D,...D,...D,...D
		]
		return {
			position: new Float32Array(pos),
			normal
		}
	}
	static setAttributes(setters, attribs) {
		setters = setters.attribSetters || setters;
		Object.keys(attribs).forEach(function(name) {
		  const setter = setters[name];
		  if (setter) {
			setter(attribs[name]);
		  }
		});
	}
	static createAttributeSetters(gl, program) {
		const attribSetters = {
		};
	
		function createAttribSetter(index) {
		/**
		 * @param {
		 * 	 {
		 *  	size||numComponents,
		 * 		[type],
		 * 		[normalize],
		 * 		[stride],
		 * 		[offset],
		 * 		[value]:[x,y,z,d]
		 *   }
		 * } b
		 */
		  return function(b) {
			  if (b.value) {
				gl.disableVertexAttribArray(index);
				switch (b.value.length) {
				  case 4:
					gl.vertexAttrib4fv(index, b.value);
					break;
				  case 3:
					gl.vertexAttrib3fv(index, b.value);
					break;
				  case 2:
					gl.vertexAttrib2fv(index, b.value);
					break;
				  case 1:
					gl.vertexAttrib1fv(index, b.value);
					break;
				  default:
					throw new Error('the length of a float constant value must be between 1 and 4!');
				}
			  } else {
				gl.bindBuffer(gl.ARRAY_BUFFER, b.buffer);
				gl.enableVertexAttribArray(index);
				gl.vertexAttribPointer(
					index, b.numComponents || b.size, b.type || gl.FLOAT, b.normalize || false, b.stride || 0, b.offset || 0);
			  }
			};
		}
	
		const numAttribs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
		for (let ii = 0; ii < numAttribs; ++ii) {
		  const attribInfo = gl.getActiveAttrib(program, ii);
		  if (!attribInfo) {
			break;
		  }
		  const index = gl.getAttribLocation(program, attribInfo.name);
		  attribSetters[attribInfo.name] = createAttribSetter(index);
		}
		console.log("attribSetters:",attribSetters);
		return attribSetters;
	}
	static createUniformSetters(gl, program) {
		let textureUnit = 0;
	
		/**
		 * Creates a setter for a uniform of the given program with it's
		 * location embedded in the setter.
		 * @param {WebGLProgram} program
		 * @param {WebGLUniformInfo} uniformInfo
		 * @returns {function} the created setter.
		 */
		function createUniformSetter(program, uniformInfo) {
		  const location = gl.getUniformLocation(program, uniformInfo.name);
		  const type = uniformInfo.type;
		  // Check if this uniform is an array
		  const isArray = (uniformInfo.size > 1 && uniformInfo.name.substr(-3) === '[0]');
		  if (type === gl.FLOAT && isArray) {
			return function(v) {
			  gl.uniform1fv(location, v);
			};
		  }
		  if (type === gl.FLOAT) {
			return function(v) {
			  gl.uniform1f(location, v);
			};
		  }
		  if (type === gl.FLOAT_VEC2) {
			return function(v) {
			  gl.uniform2fv(location, v);
			};
		  }
		  if (type === gl.FLOAT_VEC3) {
			return function(v) {
			  gl.uniform3fv(location, v);
			};
		  }
		  if (type === gl.FLOAT_VEC4) {
			return function(v) {
			  gl.uniform4fv(location, v);
			};
		  }
		  if (type === gl.INT && isArray) {
			return function(v) {
			  gl.uniform1iv(location, v);
			};
		  }
		  if (type === gl.INT) {
			return function(v) {
			  gl.uniform1i(location, v);
			};
		  }
		  if (type === gl.INT_VEC2) {
			return function(v) {
			  gl.uniform2iv(location, v);
			};
		  }
		  if (type === gl.INT_VEC3) {
			return function(v) {
			  gl.uniform3iv(location, v);
			};
		  }
		  if (type === gl.INT_VEC4) {
			return function(v) {
			  gl.uniform4iv(location, v);
			};
		  }
		  if (type === gl.BOOL) {
			return function(v) {
			  gl.uniform1iv(location, v);
			};
		  }
		  if (type === gl.BOOL_VEC2) {
			return function(v) {
			  gl.uniform2iv(location, v);
			};
		  }
		  if (type === gl.BOOL_VEC3) {
			return function(v) {
			  gl.uniform3iv(location, v);
			};
		  }
		  if (type === gl.BOOL_VEC4) {
			return function(v) {
			  gl.uniform4iv(location, v);
			};
		  }
		  if (type === gl.FLOAT_MAT2) {
			return function(v) {
			  gl.uniformMatrix2fv(location, false, v);
			};
		  }
		  if (type === gl.FLOAT_MAT3) {
			return function(v) {
			  gl.uniformMatrix3fv(location, false, v);
			};
		  }
		  if (type === gl.FLOAT_MAT4) {
			return function(v) {
			  gl.uniformMatrix4fv(location, false, v);
			};
		  }
		  if ((type === gl.SAMPLER_2D || type === gl.SAMPLER_CUBE) && isArray) {
			const units = [];
			for (let ii = 0; ii < info.size; ++ii) {
			  units.push(textureUnit++);
			}
			return function(bindPoint, units) {
			  return function(textures) {
				gl.uniform1iv(location, units);
				textures.forEach(function(texture, index) {
				  gl.activeTexture(gl.TEXTURE0 + units[index]);
				  gl.bindTexture(bindPoint, texture);
				});
			  };
			}(webglUtils.getBindPointForSamplerType(gl, type), units);
		  }
		  if (type === gl.SAMPLER_2D || type === gl.SAMPLER_CUBE) {
			return function(bindPoint, unit) {
			  return function(texture) {
				gl.uniform1i(location, unit);
				gl.activeTexture(gl.TEXTURE0 + unit);
				gl.bindTexture(bindPoint, texture);
			  };
			}(webglUtils.getBindPointForSamplerType(gl, type), textureUnit++);
		  }
		  throw ('unknown type: 0x' + type.toString(16)); // we should never get here.
		}
	
		const uniformSetters = { };
		const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
	
		for (let ii = 0; ii < numUniforms; ++ii) {
		  const uniformInfo = gl.getActiveUniform(program, ii);
		  if (!uniformInfo) {
			break;
		  }
		  let name = uniformInfo.name;
		  // remove the array suffix.
		  if (name.substr(-3) === '[0]') {
			name = name.substr(0, name.length - 3);
		  }
		  const setter = createUniformSetter(program, uniformInfo);
		  uniformSetters[name] = setter;
		}
		return uniformSetters;
	}
	static defaultShaderType = [
		'VERTEX_SHADER',
		'FRAGMENT_SHADER',
	];
	static getBindPointForSamplerType(gl, type) {
		if (type === gl.SAMPLER_2D)   return gl.TEXTURE_2D;        // eslint-disable-line
		if (type === gl.SAMPLER_CUBE) return gl.TEXTURE_CUBE_MAP;  // eslint-disable-line
		return undefined;
	}
	static createProgramFromScripts(gl, shaderScriptIds, opt_attribs, opt_locations, opt_errorCallback) {
		const shaders = [];
		for (let ii = 0; ii < shaderScriptIds.length; ++ii) {
			shaders.push(webglUtils.createShaderFromScript(gl, shaderScriptIds[ii], gl[webglUtils.defaultShaderType[ii]], opt_errorCallback));
		}
		return webglUtils.createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback);
	}

	static createShaderFromScript(gl, scriptId, opt_shaderType, opt_errorCallback) {
		let shaderSource = '';
		let shaderType;
		const shaderScript = document.getElementById(scriptId);
		if (!shaderScript){
			throw ('*** Error: 获取不到这个元素当前id:' + scriptId);
		}
		shaderSource = shaderScript.text;
		if (!opt_shaderType) {
			if (shaderScript.type === 'x-shader/x-vertex') {
				shaderType = gl.VERTEX_SHADER;
			} else if (shaderScript.type === 'x-shader/x-fragment') {
				shaderType = gl.FRAGMENT_SHADER;
			} else if (shaderType !== gl.VERTEX_SHADER && shaderType !== gl.FRAGMENT_SHADER) {
				throw ('*** Error: 没有定义标签类型 type="x-shader/x-vertex" 或者 type="x-shader/x-fragment"');
			}
		}
	  return webglUtils.loadShader(gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType,opt_errorCallback);
	}
	static isArrayBuffer(a) {
		return a.buffer && a.buffer instanceof ArrayBuffer;
	  }
	
	static guessNumComponentsFromName(name, length) {
		let numComponents;
		if (name.indexOf('coord') >= 0) {
		  numComponents = 2;
		} else if (name.indexOf('color') >= 0) {
		  numComponents = 4;
		} else {
		  numComponents = 3;  // position, normals, indices ...
		}
	
		if (length % numComponents > 0) {
		  throw 'can not guess numComponents. You should specify it.';
		}
	
		return numComponents;
	}
	static guessNumComponentsFromName(name, length) {
		let numComponents;
		if (name.indexOf('coord') >= 0) {
		  numComponents = 2;
		} else if (name.indexOf('color') >= 0) {
		  numComponents = 4;
		} else {
		  numComponents = 3;  // position, normals, indices ...
		}
	
		if (length % numComponents > 0) {
		  throw 'can not guess numComponents. You should specify it.';
		}
	
		return numComponents;
	}
	static makeTypedArray(array, name) {
		if (webglUtils.isArrayBuffer(array)) {
		  return array;
		}
	
		if (array.data && webglUtils.isArrayBuffer(array.data)) {
		  return array.data;
		}
	
		if (Array.isArray(array)) {
		  array = {
			data: array,
		  };
		}
	
		if (!array.numComponents) {
		  array.numComponents = webglUtils.guessNumComponentsFromName(name, array.length);
		}
	
		let type = array.type;
		if (!type) {
		  if (name === 'indices') {
			type = Uint16Array;
		  }
		}
		const typedArray = webglUtils.createAugmentedTypedArray(array.numComponents, array.data.length / array.numComponents | 0, type);
		typedArray.push(array.data);
		return typedArray;
	}
	static createBufferFromTypedArray(gl, array, type, drawType) {
		type = type || gl.ARRAY_BUFFER;
		const buffer = gl.createBuffer();
		gl.bindBuffer(type, buffer);
		gl.bufferData(type, array, drawType || gl.STATIC_DRAW);
		return buffer;
	}
	static createSphereBuffers(fn) {
		return function(gl) {
		  const arrays = fn.apply(this, Array.prototype.slice.call(arguments, 1));
		  return webglUtils.createBufferFunc(gl, arrays);
		};
	}
	static createBufferFunc(fn) {
		return function(gl) {
		  const arrays = fn.apply(this, Array.prototype.slice.call(arguments, 1));
		  return webglUtils.createBuffersFromArrays(gl, arrays);
		};
	}
	static createBuffersFromArrays(gl, arrays) {
		const buffers = { };
		Object.keys(arrays).forEach(function(key) {
		  const type = key === 'indices' ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
		  const array = webglUtils.makeTypedArray(arrays[key], name);
		  buffers[key] = webglUtils.createBufferFromTypedArray(gl, array, type);
		});
	
		// hrm
		if (arrays.indices) {
		  buffers.numElements = arrays.indices.length;
		} else if (arrays.position) {
		  buffers.numElements = arrays.position.length / 3;
		}
	
		return buffers;
	}
	static createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback) {
		const errFn = opt_errorCallback || webglUtils.error;
		const program = gl.createProgram();
		shaders.forEach(function(shader) {
			gl.attachShader(program, shader);
		});
		if (opt_attribs) {
			opt_attribs.forEach(function(attrib, ndx) {
				gl.bindAttribLocation(
					program,
					opt_locations ? opt_locations[ndx] : ndx,
					attrib);
				});
		}
		gl.linkProgram(program);
		
		// Check the link status
		const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
		if (!linked) {
			// something went wrong with the link
			const lastError = gl.getProgramInfoLog(program);
			errFn('Error in program linking:' + lastError);
	
			gl.deleteProgram(program);
			return null;
		}
		return program;
	}
	static error(msg) {
		if (topWindow.console) {
		  if (topWindow.console.error) {
			topWindow.console.error(msg);
		  } else if (topWindow.console.log) {
			topWindow.console.log(msg);
		  }
		}
	}
	static loadShader(gl, shaderSource, shaderType, opt_errorCallback) {
		const errFn = opt_errorCallback ||  webglUtils.error;
		// Create the shader object
		const shader = gl.createShader(shaderType);
	
		// Load the shader source
		gl.shaderSource(shader, shaderSource);
	
		// Compile the shader
		gl.compileShader(shader);
	
		// Check the compile status
		const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
		if (!compiled) {
		  // Something went wrong during compilation; get the error
		  const lastError = gl.getShaderInfoLog(shader);
		  errFn('*** Error compiling shader \'' + shader + '\':' + lastError + `\n` + shaderSource.split('\n').map((l,i) => `${i + 1}: ${l}`).join('\n'));
		  gl.deleteShader(shader);
		  return null;
		}
		return shader;
	}
	static makeStripeTexture(gl, options) {
		options = options || {};
		var width  = options.width  || 2;
		var height = options.height || 2;
		var color1 = options.color1 || "white";
		var color2 = options.color2 || "black";
		var ctx = document.createElement("canvas").getContext("2d");
		webglUtils.setCanvasSize(width, height);
	  
		ctx.fillStyle = color1 || "white";
		ctx.fillRect(0, 0, width, height);
		ctx.fillStyle = color2 || "black";
		ctx.fillRect(0, 0, width, height / 2);
	  
		return webglUtils.makeTexture(gl);
	};
	static setCanvasSize(width, height) {
		var ctx = document.createElement("canvas").getContext("2d");
		ctx.canvas.width  = width;
		ctx.canvas.height = height;
	};
	static makeTexture(gl) {
		var ctx = document.createElement("canvas").getContext("2d");
		var tex = gl.createTexture();
		gl.bindTexture(gl.TEXTURE_2D, tex);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, ctx.canvas);
		gl.generateMipmap(gl.TEXTURE_2D);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
		return tex;
	};
	static makeCheckerTexture(gl, options) {
		var ctx = document.createElement("canvas").getContext("2d");
		options = options || {};
		var width  = options.width  || 2;
		var height = options.height || 2;
		var color1 = options.color1 || "white";
		var color2 = options.color2 || "black";
	  
		webglUtils.setCanvasSize(width, height);
	  
		ctx.fillStyle = color1 || "white";
		ctx.fillRect(0, 0, width, height);
		ctx.fillStyle = color2 || "black";
		ctx.fillRect(0, 0, width / 2, height / 2);
		ctx.fillRect(width / 2, height / 2, width / 2, height / 2);
	  
		return webglUtils.makeTexture(gl);
	}
	static makeCircleTexture (gl, options) {
		var ctx = document.createElement("canvas").getContext("2d");
		options = options || {};
		var width  = options.width  || 128;
		var height = options.height || 128;
		var color1 = options.color1 || "white";
		var color2 = options.color2 || "black";
	  
		webglUtils.setCanvasSize(width, height);
	  
		var size = Math.min(width, height);
		ctx.fillStyle = color1 || "white";
		ctx.fillRect(0, 0, width, height);
		ctx.fillStyle = color2 || "black";
		ctx.save();
		ctx.translate(width / 2, height / 2);
		ctx.beginPath();
		ctx.arc(0, 0, width / 2 - 1, 0, Math.PI * 2);
		ctx.fill();
		ctx.fillStyle = color1 || "white";
		ctx.beginPath();
		ctx.arc(0, 0, width / 4 - 1, 0, Math.PI * 2);
		ctx.fill();
		ctx.restore();
	  
		return webglUtils.makeTexture(gl);
	}
	static resizeCanvasToDisplaySize(canvas, multiplier) {
		multiplier = multiplier || 1;
		const width  = canvas.clientWidth  * multiplier | 0;
		const height = canvas.clientHeight * multiplier | 0;
		if (canvas.width !== width ||  canvas.height !== height) {
		  canvas.width  = width;
		  canvas.height = height;
		  return true;
		}
		return false;
	}
	static setUniforms(setters, ...values) {
		setters = setters.uniformSetters || setters;
		for (const uniforms of values) {
		  Object.keys(uniforms).forEach(function(name) {
			const setter = setters[name];
			if (setter) {
			  setter(uniforms[name]);
			}
		  });
		}
	  }
})
